<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebSocket 聊天室</title>
    <style>
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
            margin: 0;
            padding: 20px;
            background-color: #f5f5f5;
        }
        .container {
            max-width: 800px;
            margin: 0 auto;
            background-color: white;
            border-radius: 8px;
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
            padding: 20px;
        }
        .chat-area {
            height: 400px;
            overflow-y: auto;
            border: 1px solid #ddd;
            border-radius: 4px;
            padding: 10px;
            margin-bottom: 20px;
        }
        .message {
            margin-bottom: 10px;
            padding: 8px 12px;
            border-radius: 4px;
            max-width: 80%;
            word-wrap: break-word;
            position: relative;
            animation: fadeIn 0.3s ease-in-out;
        }
        @keyframes fadeIn {
            from { opacity: 0; transform: translateY(10px); }
            to { opacity: 1; transform: translateY(0); }
        }
        .message.system {
            background-color: #f8f9fa;
            color: #6c757d;
            margin-left: auto;
            margin-right: auto;
            text-align: center;
            max-width: 90%;
            font-size: 0.9em;
            padding: 4px 12px;
        }
        .message.self {
            background-color: #007bff;
            color: white;
            margin-left: auto;
            border-radius: 15px 15px 0 15px;
        }
        .message.other {
            background-color: #e9ecef;
            color: #212529;
            margin-right: auto;
            border-radius: 15px 15px 15px 0;
        }
        .message .time {
            font-size: 0.8em;
            color: #666;
            margin-top: 4px;
        }
        .input-area {
            display: flex;
            gap: 10px;
            margin-bottom: 20px;
        }
        input[type="text"] {
            flex: 1;
            padding: 8px 12px;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-size: 14px;
        }
        button {
            padding: 8px 16px;
            background-color: #007bff;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 14px;
            transition: background-color 0.2s;
        }
        button:hover {
            background-color: #0056b3;
        }
        button:disabled {
            background-color: #ccc;
            cursor: not-allowed;
        }
        .room-list {
            margin-bottom: 20px;
            display: flex;
            gap: 10px;
            flex-wrap: wrap;
        }
        .room {
            padding: 6px 12px;
            background-color: #e9ecef;
            border-radius: 4px;
            cursor: pointer;
            transition: all 0.2s;
        }
        .room:hover {
            background-color: #dee2e6;
        }
        .room.active {
            background-color: #007bff;
            color: white;
        }
        .status {
            margin-bottom: 10px;
            padding: 8px;
            border-radius: 4px;
            text-align: center;
            font-weight: 500;
        }
        .status.connected {
            background-color: #d4edda;
            color: #155724;
        }
        .status.disconnected {
            background-color: #f8d7da;
            color: #721c24;
        }
        .status.connecting {
            background-color: #fff3cd;
            color: #856404;
        }
    </style>
</head>
<body>
    <div class="container">
        <div id="status" class="status disconnected">未连接</div>
        <div class="room-list" id="roomList">
            <div class="room active" data-room="">大厅</div>
            <div class="room" data-room="room1">房间1</div>
            <div class="room" data-room="room2">房间2</div>
        </div>
        <div class="chat-area" id="chatArea"></div>
        <div class="input-area">
            <input type="text" id="messageInput" placeholder="输入消息..." disabled>
            <button id="sendButton" disabled>发送</button>
        </div>
    </div>

    <script>
        class WebSocketClient {
            constructor() {
                this.ws = null;
                this.currentRoom = '';
                this.reconnectAttempts = 0;
                this.maxReconnectAttempts = 3;
                this.reconnectDelay = 5000;
                this.lastPingTime = Date.now();
                this.pingInterval = null;
                this.messageQueue = [];
                this.displayedMessages = null;

                this.connect();
                this.setupEventListeners();
            }

            connect() {
                this.updateStatus('connecting', '正在连接...');

                // 清理之前的连接
                if (this.ws) {
                    this.ws.close();
                    this.ws = null;
                }

                const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
                const wsUrl = `${protocol}//${window.location.host}/ws`;
                console.log('Connecting to WebSocket:', wsUrl);

                try {
                    this.ws = new WebSocket(wsUrl);
                    
                    // 设置二进制类型为 blob
                    this.ws.binaryType = 'blob';
                    
                    // 添加事件监听器
                    this.ws.onopen = (event) => {
                        console.log('WebSocket connected:', event);
                        this.onOpen();
                    };
                    
                    this.ws.onclose = (event) => {
                        console.log('WebSocket closed:', event);
                        this.onClose();
                    };
                    
                    this.ws.onmessage = (event) => {
                        console.log('WebSocket message received:', event);
                        this.onMessage(event);
                    };
                    
                    this.ws.onerror = (error) => {
                        console.error('WebSocket error:', error);
                        this.onError(error);
                    };

                    // 设置连接超时
                    this.connectTimeout = setTimeout(() => {
                        if (this.ws.readyState !== WebSocket.OPEN) {
                            console.error('WebSocket connection timeout');
                            this.ws.close();
                        }
                    }, 5000);
                } catch (error) {
                    console.error('Error creating WebSocket:', error);
                    this.updateStatus('disconnected', '连接失败：' + error.message);
                }
            }

            onOpen() {
                console.log('WebSocket connection established');
                clearTimeout(this.connectTimeout);
                this.updateStatus('connected', '已连接');
                this.reconnectAttempts = 0;
                this.enableInputs();
                this.startPing();
                this.flushMessageQueue();

                // 如果有房间，重新加入
                if (this.currentRoom) {
                    console.log('Rejoining room:', this.currentRoom);
                    this.joinRoom(this.currentRoom);
                }
            }

            onClose() {
                console.log('WebSocket connection closed');
                clearTimeout(this.connectTimeout);
                this.updateStatus('disconnected', '连接断开');
                this.disableInputs();
                this.stopPing();

                if (this.reconnectAttempts < this.maxReconnectAttempts) {
                    this.reconnectAttempts++;
                    const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
                    console.log(`Attempting to reconnect in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
                    this.updateStatus('connecting', `正在尝试重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts})...`);
                    setTimeout(() => this.connect(), delay);
                } else {
                    console.log('Max reconnection attempts reached');
                    this.updateStatus('disconnected', '重连失败，请刷新页面重试');
                }
            }

            onMessage(event) {
                try {
                    const message = JSON.parse(event.data);
                    console.log('Received message:', message);
                    this.lastPingTime = Date.now();

                    if (message.event === 'pong') {
                        return;
                    }

                    // 检查消息是否已经显示过
                    const messageId = `${message.time}-${message.from}-${message.data}`;
                    if (this.displayedMessages && this.displayedMessages.has(messageId)) {
                        console.log('Duplicate message detected:', messageId);
                        return;
                    }

                    // 记录已显示的消息
                    if (!this.displayedMessages) {
                        this.displayedMessages = new Set();
                    }
                    this.displayedMessages.add(messageId);

                    // 限制已显示消息的记录数量
                    if (this.displayedMessages.size > 1000) {
                        const oldestMessages = Array.from(this.displayedMessages).slice(0, 500);
                        this.displayedMessages = new Set(oldestMessages);
                    }

                    this.displayMessage(message);
                } catch (error) {
                    console.error('Error parsing message:', error);
                }
            }

            onError(error) {
                console.error('WebSocket error:', error);
            }

            startPing() {
                this.stopPing();
                this.pingInterval = setInterval(() => {
                    if (this.ws && this.ws.readyState === WebSocket.OPEN) {
                        this.sendMessage({
                            type: 1,
                            event: 'ping',
                            time: new Date()
                        });
                    }
                }, 30000);
            }

            stopPing() {
                if (this.pingInterval) {
                    clearInterval(this.pingInterval);
                    this.pingInterval = null;
                }
            }

            sendMessage(message) {
                if (this.ws && this.ws.readyState === WebSocket.OPEN) {
                    this.ws.send(JSON.stringify(message));
                    return true;
                } else {
                    this.messageQueue.push(message);
                    return false;
                }
            }

            flushMessageQueue() {
                while (this.messageQueue.length > 0) {
                    const message = this.messageQueue.shift();
                    this.sendMessage(message);
                }
            }

            displayMessage(message) {
                const chatArea = document.getElementById('chatArea');
                const messageDiv = document.createElement('div');

                if (message.event === 'error') {
                    messageDiv.className = 'message system';
                    messageDiv.textContent = `错误: ${message.error}`;
                } else if (message.event === 'system') {
                    messageDiv.className = 'message system';
                    messageDiv.textContent = message.data;
                } else {
                    const isCurrentUser = message.from === this.ws.id;
                    messageDiv.className = `message ${isCurrentUser ? 'self' : 'other'}`;

                    const contentDiv = document.createElement('div');
                    contentDiv.className = 'content';
                    contentDiv.textContent = message.data;
                    messageDiv.appendChild(contentDiv);

                    const timeDiv = document.createElement('div');
                    timeDiv.className = 'time';
                    timeDiv.textContent = new Date(message.time).toLocaleTimeString();
                    messageDiv.appendChild(timeDiv);
                }

                chatArea.appendChild(messageDiv);
                chatArea.scrollTop = chatArea.scrollHeight;

                // 限制消息数量
                while (chatArea.children.length > 200) {
                    chatArea.removeChild(chatArea.firstChild);
                }

                // 如果窗口不在焦点，发送通知
                if (!document.hasFocus()) {
                    this.sendNotification(message);
                }
            }

            sendNotification(message) {
                if (!("Notification" in window)) return;

                if (Notification.permission === "granted") {
                    this.createNotification(message);
                } else if (Notification.permission !== "denied") {
                    Notification.requestPermission().then(permission => {
                        if (permission === "granted") {
                            this.createNotification(message);
                        }
                    });
                }
            }

            createNotification(message) {
                const title = message.event === 'system' ? '系统消息' : `来自 ${message.from} 的消息`;
                const notification = new Notification(title, {
                    body: message.data,
                    icon: '/favicon.ico'
                });

                notification.onclick = () => {
                    window.focus();
                    notification.close();
                };

                setTimeout(() => notification.close(), 5000);
            }

            setupEventListeners() {
                const messageInput = document.getElementById('messageInput');
                const sendButton = document.getElementById('sendButton');
                const roomList = document.getElementById('roomList');

                messageInput.addEventListener('keypress', (event) => {
                    if (event.key === 'Enter' && !event.shiftKey) {
                        event.preventDefault();
                        this.sendChatMessage();
                    }
                });

                sendButton.addEventListener('click', () => {
                    this.sendChatMessage();
                });

                roomList.addEventListener('click', (event) => {
                    const roomElement = event.target.closest('.room');
                    if (roomElement) {
                        this.changeRoom(roomElement.dataset.room);
                    }
                });
            }

            sendChatMessage() {
                const messageInput = document.getElementById('messageInput');
                const content = messageInput.value.trim();

                if (content) {
                    const message = {
                        type: 1,
                        event: 'chat',
                        data: content,
                        room_id: this.currentRoom,
                        time: new Date()
                    };

                    // 只发送消息，不在本地显示
                    // 等待服务器的广播消息再显示
                    if (this.sendMessage(message)) {
                        messageInput.value = '';
                    }
                }
            }

            changeRoom(newRoom) {
                if (this.currentRoom === newRoom) return;

                // 离开当前房间
                if (this.currentRoom) {
                    this.sendMessage({
                        type: 1,
                        event: 'leave_room',
                        data: this.currentRoom,
                        time: new Date()
                    });
                }

                this.currentRoom = newRoom;
                document.querySelectorAll('.room').forEach(room => {
                    room.classList.toggle('active', room.dataset.room === newRoom);
                });

                // 加入新房间
                if (newRoom) {
                    this.sendMessage({
                        type: 1,
                        event: 'join_room',
                        data: newRoom,
                        time: new Date()
                    });
                }

                // 清空聊天区域
                const chatArea = document.getElementById('chatArea');
                chatArea.innerHTML = '';

                // 添加房间切换提示
                this.displayMessage({
                    event: 'system',
                    data: newRoom ? `已加入房间: ${newRoom}` : '已返回大厅',
                    time: new Date()
                });
            }

            updateStatus(state, message) {
                const statusDiv = document.getElementById('status');
                statusDiv.className = `status ${state}`;
                statusDiv.textContent = message;
            }

            enableInputs() {
                document.getElementById('messageInput').disabled = false;
                document.getElementById('sendButton').disabled = false;
            }

            disableInputs() {
                document.getElementById('messageInput').disabled = true;
                document.getElementById('sendButton').disabled = true;
            }
        }

        // 初始化 WebSocket 客户端
        const client = new WebSocketClient();
    </script>
</body>
</html> 